/*-------------------------------------------------------------------------
 *
 * pqexpbuffer.c
 *
 * PQExpBuffer 提供了一种无限扩展的字符串数据类型。
 * 它可以用于缓冲普通的 C 字符串（以 null 结尾的文本）或任意二进制数据。所有存储都是使用 malloc() 分配的。
 *
 * 此模块本质上与后端的 StringInfo 数据类型相同，但它旨在用于前端 libpq 和客户端应用程序。
 * 因此，它不依赖于 palloc()、elog() 或 psprintf.c，因为这些在出错时会 exit()。
 *
 * 它依赖于 vsnprintf(); 如果配置找到 libc 不提供可用的 vsnprintf()，则会将我们自己的实现的副本链接到 libpq 中。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * src/interfaces/libpq/pqexpbuffer.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres_fe.h"

#include <limits.h>

#include "pqexpbuffer.h"

#ifdef WIN32
#include "win32.h"
#endif


/* 所有“损坏”的 PQExpBuffers 都指向这个字符串。 */
static const char oom_buffer[1] = "";

/* 需要一个 char * 以兼容 unconstify() */
static const char *oom_buffer_ptr = oom_buffer;

static bool fc_appendPQExpBufferVA(PQExpBuffer fc_str, const char *fc_fmt, va_list fc_args) pg_attribute_printf(2, 0);


/*
 * markPQExpBufferBroken
 *
 * 如果 PQExpBuffer 还不是“损坏”状态，则将其置为“损坏”状态。
 */
static void fc_markPQExpBufferBroken(PQExpBuffer fc_str)
{
	if (fc_str->data != oom_buffer)
		free(fc_str->data);

	/*
	 * 这里去掉 const 有点丑陋，但似乎比将 oom_buffer 标记为 const 更可取。
	 * 我们希望这样做以鼓励编译器将 oom_buffer 放入只读存储中，
	 * 这样任何试图在损坏的 PQExpBuffer 上涂抹的操作都会失败。
	 */
	fc_str->data = unconstify(char *, oom_buffer_ptr);
	fc_str->len = 0;
	fc_str->maxlen = 0;
}

/*
 * createPQExpBuffer
 *
 * 创建一个空的 'PQExpBufferData' 并返回指向它的指针。
 */
PQExpBuffer
createPQExpBuffer(void)
{
	PQExpBuffer fc_res;

	fc_res = (PQExpBuffer) malloc(sizeof(PQExpBufferData));
	if (fc_res != NULL)
		initPQExpBuffer(fc_res);

	return fc_res;
}

/*
 * initPQExpBuffer
 *
 * 初始化 PQExpBufferData 结构（内容以前未定义），以描述一个空字符串。
 */
void initPQExpBuffer(PQExpBuffer fc_str)
{
	fc_str->data = (char *) malloc(INITIAL_EXPBUFFER_SIZE);
	if (fc_str->data == NULL)
	{
		fc_str->data = unconstify(char *, oom_buffer_ptr); /* 请参见上面的注释 */
		fc_str->maxlen = 0;
		fc_str->len = 0;
	}
	else
	{
		fc_str->maxlen = INITIAL_EXPBUFFER_SIZE;
		fc_str->len = 0;
		fc_str->data[0] = '\0';
	}
}

/*
 * destroyPQExpBuffer(str);
 *
 *		free() 数据缓冲区和 PQExpBufferData。
 *		这是 createPQExpBuffer() 的逆操作。
 */
void destroyPQExpBuffer(PQExpBuffer fc_str)
{
	if (fc_str)
	{
		termPQExpBuffer(fc_str);
		free(fc_str);
	}
}

/*
 * termPQExpBuffer(str)
 *		free() 数据缓冲区，但不释放 PQExpBufferData 本身。
 *		这是 initPQExpBuffer() 的逆操作。
 */
void termPQExpBuffer(PQExpBuffer fc_str)
{
	if (fc_str->data != oom_buffer)
		free(fc_str->data);
	/* 只是为了运气，让缓冲区有效地为空。 */
	fc_str->data = unconstify(char *, oom_buffer_ptr); /* 请参见上面的注释 */
	fc_str->maxlen = 0;
	fc_str->len = 0;
}

/*
 * resetPQExpBuffer
 *		将 PQExpBuffer 重置为空
 *
 * 注意：如果可能，返回“损坏”的 PQExpBuffer 到正常状态。
 */
void resetPQExpBuffer(PQExpBuffer fc_str)
{
	if (fc_str)
	{
		if (fc_str->data != oom_buffer)
		{
			fc_str->len = 0;
			fc_str->data[0] = '\0';
		}
		else
		{
			/* 尝试重新初始化为有效状态 */
			initPQExpBuffer(fc_str);
		}
	}
}

/*
 * enlargePQExpBuffer
 * 确保缓冲区中有足够的空间以容纳“所需”的更多字节
 * （“所需”不包括结束的空字符）。
 *
 * 如果成功返回 1，如果扩大缓冲区失败则返回 0。 （在后者的情况下
 * 缓冲区保持在“损坏”状态。）
 */
int enlargePQExpBuffer(PQExpBuffer fc_str, size_t fc_needed)
{
	size_t		fc_newlen;
	char	   *fc_newdata;

	if (PQExpBufferBroken(fc_str))
		return 0;				/* 已经失败 */

	/*
	 * 防止荒谬的“所需”值，这可能发生在我们被传入
	 * 虚假的数据时。没有这个，我们可能会在下面发生溢出或无限循环。
	 */
	if (fc_needed >= ((size_t) INT_MAX - fc_str->len))
	{
		fc_markPQExpBufferBroken(fc_str);
		return 0;
	}

	fc_needed += fc_str->len + 1;		/* 现在所需的总空间 */

	/* 由于上面的测试，我们现在有所需 <= INT_MAX */

	if (fc_needed <= fc_str->maxlen)
		return 1;				/* 已经有足够的空间了 */

	/*
	 * 我们不想在每次附加时只分配一点更多的空间；
	 * 为了提高效率，每次溢出时将缓冲区大小加倍。
	 * 实际上，如果“所需”的大小很大，我们可能需要更多地加倍...
	 */
	fc_newlen = (fc_str->maxlen > 0) ? (2 * fc_str->maxlen) : 64;
	while (fc_needed > fc_newlen)
		fc_newlen = 2 * fc_newlen;

	/*
	 * 如果我们超过了 INT_MAX，则将其限制为 INT_MAX。
	 * 注意我们在这里假设 INT_MAX <= UINT_MAX/2，否则上面的循环可能会溢出。
	 * 我们仍然会有 newlen >= needed。
	 */
	if (fc_newlen > (size_t) INT_MAX)
		fc_newlen = (size_t) INT_MAX;

	fc_newdata = (char *) realloc(fc_str->data, fc_newlen);
	if (fc_newdata != NULL)
	{
		fc_str->data = fc_newdata;
		fc_str->maxlen = fc_newlen;
		return 1;
	}

	fc_markPQExpBufferBroken(fc_str);
	return 0;
}

/*
 * printfPQExpBuffer
 * 在 fmt（类似 sprintf 的格式字符串）的控制下格式化文本数据
 * 并将其插入到 str 中。如果需要，会为 str 分配更多空间。
 * 这是一个方便的例程，做与 resetPQExpBuffer() 后跟 appendPQExpBuffer() 相同的事情。
 */
void printfPQExpBuffer(PQExpBuffer fc_str, const char *fc_fmt,...)
{
	int			fc_save_errno = errno;
	va_list		fc_args;
	bool		fc_done;

	resetPQExpBuffer(fc_str);

	if (PQExpBufferBroken(fc_str))
		return;					/* 已经失败 */

	/* 循环以防我们在扩大缓冲区后需要重试。 */
	do
	{
		errno = fc_save_errno;
		va_start(fc_args, fc_fmt);
		fc_done = fc_appendPQExpBufferVA(fc_str, fc_fmt, fc_args);
		va_end(fc_args);
	} while (!fc_done);
}

/*
 * appendPQExpBuffer
 *
 * 根据 fmt（类似 sprintf 的格式字符串）的控制格式化文本数据
 * 并将其附加到 str 中已经存在的内容上。如果需要，则为 str 分配更多空间。
 * 这有点像 sprintf 和 strcat 的结合体。
 */
void appendPQExpBuffer(PQExpBuffer fc_str, const char *fc_fmt,...)
{
	int			fc_save_errno = errno;
	va_list		fc_args;
	bool		fc_done;

	if (PQExpBufferBroken(fc_str))
		return;					/* 已经失败 */

	/* 循环以防我们在扩大缓冲区后需要重试。 */
	do
	{
		errno = fc_save_errno;
		va_start(fc_args, fc_fmt);
		fc_done = fc_appendPQExpBufferVA(fc_str, fc_fmt, fc_args);
		va_end(fc_args);
	} while (!fc_done);
}

/*
 * appendPQExpBufferVA
 * printfPQExpBuffer/appendPQExpBuffer 的共享实现。
 * 尝试格式化数据并将其附加到 str。如果完成（无论是成功还是严重失败），则返回 true，
 * 如果需要重试，则返回 false。
 *
 * 注意：调用者必须确保在循环中保留其入口时的 errno，
 * 以防 fmt 包含 "%m"。
 */
static bool fc_appendPQExpBufferVA(PQExpBuffer fc_str, const char *fc_fmt, va_list fc_args)
{
	size_t		fc_avail;
	size_t		fc_needed;
	int			fc_nprinted;

	/*
	 * 尝试将给定字符串格式化到可用空间中；但如果几乎没有空间，
	 * 就不必尝试，先扩大缓冲区。
	 */
	if (fc_str->maxlen > fc_str->len + 16)
	{
		fc_avail = fc_str->maxlen - fc_str->len;

		fc_nprinted = vsnprintf(fc_str->data + fc_str->len, fc_avail, fc_fmt, fc_args);

		/*
		 * 如果 vsnprintf 报告错误，则失败（我们假设这意味着
		 * 格式字符串有什么问题）。
		 */
		if (unlikely(fc_nprinted < 0))
		{
			fc_markPQExpBufferBroken(fc_str);
			return true;
		}

		if ((size_t) fc_nprinted < fc_avail)
		{
			/* 成功。注意 nprinted 不包括尾随空字符。 */
			fc_str->len += fc_nprinted;
			return true;
		}

		/*
		 * 我们假设一个符合 C99 的 vsnprintf，因此相信它对
		 * 所需空间的估计，并为尾随 null 增加一个。（如果它错误，
		 * 逻辑仍然有效，但我们可能会循环多次。）
		 *
		 * 如果所需空间超过 INT_MAX，则中止，因为 str->maxlen
		 * 不能表示更多。
		 */
		if (unlikely(fc_nprinted > INT_MAX - 1))
		{
			fc_markPQExpBufferBroken(fc_str);
			return true;
		}
		fc_needed = fc_nprinted + 1;
	}
	else
	{
		/*
		 * 我们必须猜测需要扩大多少，因为我们跳过了
		 * 格式化工作。幸运的是，由于 enlargePQExpBuffer
		 * 对 2 的幂大小的偏好，这个数字不是特别敏感；
		 * 整体效果是我们将在尝试运行 vsnprintf 之前加倍缓冲区大小，
		 * 这似乎是合理的。
		 */
		fc_needed = 32;
	}

	/* 增加缓冲区大小并重试。 */
	if (!enlargePQExpBuffer(fc_str, fc_needed))
		return true;			/* 哎呀，内存不足 */

	return false;
}

/*
 * appendPQExpBufferStr
 * 将给定字符串附加到 PQExpBuffer，如果必要则分配更多空间。
 */
void appendPQExpBufferStr(PQExpBuffer fc_str, const char *fc_data)
{
	appendBinaryPQExpBuffer(fc_str, fc_data, strlen(fc_data));
}

/*
 * appendPQExpBufferChar
 * 将单个字节附加到 str。
 * 类似于 appendPQExpBuffer(str, "%c", ch)，但速度更快。
 */
void appendPQExpBufferChar(PQExpBuffer fc_str, char fc_ch)
{
	/* 如果需要，增加更多空间 */
	if (!enlargePQExpBuffer(fc_str, 1))
		return;

	/* 好的，附加字符 */
	fc_str->data[fc_str->len] = fc_ch;
	fc_str->len++;
	fc_str->data[fc_str->len] = '\0';
}

/*
 * appendBinaryPQExpBuffer
 *
 * 将任意二进制数据附加到 PQExpBuffer，如果必要则分配更多空间。
 */
void appendBinaryPQExpBuffer(PQExpBuffer fc_str, const char *fc_data, size_t fc_datalen)
{
	/* 如果需要，增加更多空间 */
	if (!enlargePQExpBuffer(fc_str, fc_datalen))
		return;

	/* 好的，附加数据 */
	memcpy(fc_str->data + fc_str->len, fc_data, fc_datalen);
	fc_str->len += fc_datalen;

	/*
	 * 保持尾随 null 不变，即使对于二进制数据来说它可能是无用的...
	 */
	fc_str->data[fc_str->len] = '\0';
}
